﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

using Weazel.Badger.Vhdl.Types;

namespace Weazel.Badger.Vhdl
{
  public class Signal : TypedComponent, ISignalDeclaration
  {
    /// <summary>
    /// Set of types this signal may have
    /// </summary>
    private Weazel.Badger.Vhdl.Types.TypeSet possibleTypes;

    /// <summary>
    /// List of statements assigning this signal
    /// </summary>
    protected List<Statements.SignalAssignmentStatement> assignStatements =
      new List<Weazel.Badger.Vhdl.Statements.SignalAssignmentStatement>();

    /// <summary>
    /// Variable used to assign signals unique names.
    /// </summary>
    private static int signals = 0;

    /// <summary>
    /// All signal assignment statements assigning this signal should
    /// be present in the 'assignStatements' list. 
    /// </summary>
    /// <param name="stmt">Assign statement assigning this signal</param>
    public virtual void AddAssignmentStatement(Statements.SignalAssignmentStatement stmt)
    {
      if (stmt.Expression == null)
        throw new NullReferenceException("driver expression cannot be null");
      
      if (!assignStatements.Contains(stmt))
        assignStatements.Add(stmt);
    }
   
    private SignalAttribute eventAttribute;
    public SignalAttribute EventAttribute
    {
      get
      {
        if (eventAttribute == null)
          eventAttribute = new SignalAttribute(this, new Types.Boolean(), "event");

        return eventAttribute;
      }      
    }

    private SignalAttribute stableAttribute;
    public SignalAttribute StableAttribute
    {
      get
      {
        if (stableAttribute == null)
          stableAttribute = new SignalAttribute(this, new Types.Boolean(), "stable");

        return stableAttribute;
      }
    }    

    private string name;
    public string Name
    {
      get
      {
        return name;
      }

      set
      {
        name = value;
      }
    }

    public override bool VerifyType(ref List<VerifyMessage> messages)
    {
      if (this.Type == Types.Types.Undefined)
      {
        messages.Add(new VerifyMessage(string.Format("no type set for signal '{0}'", name)));
        return false;
      }

      return true;
    }

		public override void SetActualType(Entity entity, Process process, Statements.StatementCollection statements, Vhdl.Types.Type type)
    {
      if (resolved)
      {
        if (this.Type != type)
          throw new Exception();

        return;
      }
      
      for (int i = 0; i < assignStatements.Count; i++)
      {
        if (!(assignStatements[i].Expression is Expressions.ClearSignalExpression))
        {
          TypeSet from = assignStatements[i].Expression.GetPossibleTypes();

          if (from == null)
          {
            string message =
              string.Format(
                "GetPossibleTypes() for assignment expression of signal '{0}' returned null.",
                name
              );

            throw new NullReferenceException(message);
          }

          if (from.Count == 0)
          {
            string message =
              string.Format("Assignment statement for signal '{0}' returned no possible types", name);

            throw new TypeResolveException(message);
          }

          TypeConversionResult r = 
            TypeConverter.GetConversion(
              from, 
              type, 
              assignStatements[i].Expression.GezelType,
              GezelType,
              false
            );

          // if r is null no conversion exists
          if (r == null)
          {
            string message =
              string.Format(
                "no conversions exists from set {0} to type {1}",
                assignStatements[i].Expression.GetPossibleTypes(),
                type
              );

            throw new TypeResolveException(message);
          }

          // apply any conversions necessary
          assignStatements[i].Expression =
						ConversionBuilder.ApplyConversion(
							r, 
							statements, 
							entity,
							process,
							assignStatements[i].Expression
						);
        }

				assignStatements[i].Expression.SetActualType(entity, process, statements, type);
      }      

      this.Type = type;

      resolved = true;
    }

    /// <summary>
    /// Returns the possible types this signal may have
    /// </summary>
    /// <returns>Possible type of this signal</returns>
    public override TypeSet GetPossibleTypes()
    {
      return this.possibleTypes;
    }

    /// <summary>
    /// Create a new signal of a specificed type with an
    /// auto generated name.
    /// </summary>
    /// <param name="type">type of signal</param>
    public Signal(Vhdl.Types.Type type, GezelType gezelType) : 
      this(new TypeSet(type), type, gezelType, "signal_" + (signals++).ToString())
    {
      // empty
    }

    /// <summary>
    /// Create a new signal of a specified type with a specific
    /// name.
    /// </summary>
    /// <param name="type">type of signal</param>
    /// <param name="name">name of signal</param>
    public Signal(Vhdl.Types.Type type, GezelType gezelType, string name) :
      this(new TypeSet(type), type, gezelType, name)
    {
      // empty
    }

    /// <summary>
    /// Create a new signal. 
    /// TODO: Are these used?
    /// </summary>
    /// <param name="types"></param>
    /// <param name="type"></param>
    public Signal(TypeSet types, Types.Type type, GezelType gezelType) : 
      this(types, type, gezelType, "signal_" + (signals++).ToString())
    {
      // empty
    }

    public Signal(TypeSet types, Types.Type type, GezelType gezelType, string name) : base(type, gezelType)
    {
      // check that the name is valid (todo: add more checking!)
      if (name == "" || name == " " || name == "null")
        throw new NotSupportedException("Signal name cannot be empty");

      this.name = name;
      this.possibleTypes = types;
    }

    /// <summary>
    /// Returns a string representation of this signal.   
    /// </summary>
    /// <returns>Name of this signal</returns>
    public override string ToString()
    {
      return name;
    }

    public void Write(System.CodeDom.Compiler.IndentedTextWriter writer)
    {
      string type = !(this.Type == Types.Types.Undefined) ? this.Type.ToString() : "TYPE NOT SET";

      writer.WriteLine(String.Format("signal {0} : {1};", name, type));
    }
  }
}
